#include "Model.hpp"

#include "objLoader.h"
#include "Material.hpp"
#include "Context.hpp"

Model::Model(const std::string & filename)
{
  
  Program * normalProg = g_context.useProgram(Context::Normal);
  
  m_al_vertex   = normalProg->getAttributeLocation("VertexPosition");
  m_al_normal   = normalProg->getAttributeLocation("VertexNormal");
  m_al_texCoord = normalProg->getAttributeLocation("VertexTexCoord");
  
  m_ul_Ka = normalProg->getUniformLocation("Material.Ka");
  m_ul_Kd = normalProg->getUniformLocation("Material.Kd");
  m_ul_Ks = normalProg->getUniformLocation("Material.Ks");
  m_ul_s  = normalProg->getUniformLocation("Material.Shininess");
  
  m_ul_model    = normalProg->getUniformLocation("MatrixModel");
  m_ul_texMap   = normalProg->getUniformLocation("TexMap");
  
  m_objLoader = new objLoader();
  m_objLoader->load(filename.c_str());
  
  /*
   * NOTE:  OpenGL can’t be told to use one index for the position,
   * another for the texture, and another for the normal. So the approach
   * I took for is to make a standard, non-indexed mesh. Ideally we would
   * spend the time converting the many arrays with many indices arrays 
   * into many arrays with one single indice array
   */

  
  
#warning "model constructor needs to utilise indices"
  GLfloat * vertices  = new GLfloat[m_objLoader->faceCount * 3 * 3];
  GLfloat * normals   = new GLfloat[m_objLoader->faceCount * 3 * 3];
  GLfloat * texCoords = new GLfloat[m_objLoader->faceCount * 3 * 2];
  
  m_materialCount = m_objLoader->materialCount;
  m_materials = static_cast<Material*> ( // allocate but dont construct
      ::operator new (sizeof(Material[m_materialCount]))
    );
  
  GLfloat * vp = vertices;
  GLfloat * np = normals;
  GLfloat * tp = texCoords;
 
  int offset = 0;
  for (int m = 0; m < m_objLoader->materialCount; ++m) {
    obj_material * material = m_objLoader->materialList[m];
    new (&m_materials[m]) Material(material);
    m_materials[m].m_offset = offset;
    int vertexCount = 0;
    
    for (int f = 0; f < m_objLoader->faceCount; ++f) {
      obj_face * face = m_objLoader->faceList[f];
 
      if (face->material_index != m)
        continue;
      for (int i = 0; i < 3; ++i) {
        offset++;
        vertexCount++;
        
        int vi = face->vertex_index[i];
        *vp++ = m_objLoader->vertexList[vi]->e[0];
        *vp++ = m_objLoader->vertexList[vi]->e[1];
        *vp++ = m_objLoader->vertexList[vi]->e[2];
      
        int ni = face->normal_index[i];
        *np++ = m_objLoader->normalList[ni]->e[0];
        *np++ = m_objLoader->normalList[ni]->e[1];
        *np++ = m_objLoader->normalList[ni]->e[2];
       
        int ti = face->texture_index[i];
        if (ti >= 0) {
          *tp++ = m_objLoader->textureList[ti]->e[0];
          *tp++ = m_objLoader->textureList[ti]->e[1];
        } else {
          *tp++ = 0.0f;
          *tp++ = 0.0f;
        }
        
      }    
    }
    m_materials[m].m_vertexCount = vertexCount;
  }
  
  glGenVertexArrays(1, &m_vaoHandle);
  glBindVertexArray(m_vaoHandle);
  
  glGenBuffers(NumVbos, m_vboHandles);
    
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[Vertices]);
  glBufferData(GL_ARRAY_BUFFER,
               m_objLoader->faceCount * 3 * 3 * sizeof(GLfloat),
               vertices, GL_STATIC_DRAW);
  glEnableVertexAttribArray(m_al_vertex);
  glVertexAttribPointer(m_al_vertex,
                        3, GL_FLOAT, GL_FALSE, 0, 0);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[Normals]);
  glBufferData(GL_ARRAY_BUFFER,
               m_objLoader->faceCount * 3 * 3 * sizeof(GLfloat),
               normals, GL_STATIC_DRAW);
  glEnableVertexAttribArray(m_al_normal);
  glVertexAttribPointer(m_al_normal,
                        3, GL_FLOAT, GL_FALSE, 0, 0);
  
  glBindBuffer(GL_ARRAY_BUFFER, m_vboHandles[TexCoords]);
  glBufferData(GL_ARRAY_BUFFER,
               m_objLoader->faceCount * 3 * 3 * sizeof(GLfloat),
               texCoords, GL_STATIC_DRAW);
  glEnableVertexAttribArray(m_al_texCoord);
  glVertexAttribPointer(m_al_texCoord,
                        2, GL_FLOAT, GL_FALSE, 0, 0);
  
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  

  float inf = 1.0f/0.0f;
  vec3 min(inf,inf,inf);
  vec3 max(-inf,-inf,-inf);
  for(int i = 0; i < m_objLoader->vertexCount; ++i) {
    for(int j = 0; j < 3; ++j) {
      if(max[j] < m_objLoader->vertexList[i]->e[j])
        max[j] = m_objLoader->vertexList[i]->e[j];
      if(min[j] > m_objLoader->vertexList[i]->e[j])
        min[j] = m_objLoader->vertexList[i]->e[j];
    }
  }
  
  m_bounds.pos = (max + min)/2.0f;
  m_bounds.dis = (max - min)/2.0f;
 
}

Model::~Model()
{
 
}

void Model::draw()
{
  g_context.useProgram(Context::Normal);
  glBindVertexArray(m_vaoHandle);  
  
  for (int m = 0; m < m_materialCount; ++m) {
    
    glUniform3fv(m_ul_Ka, 1, &m_materials[m].m_ambient[0]);
    glUniform3fv(m_ul_Kd, 1, &m_materials[m].m_diffuse[0]);
    glUniform3fv(m_ul_Ks, 1, &m_materials[m].m_specular[0]);
    glUniform1f(m_ul_s, m_materials[m].m_shininess);
    
    if (m_materials[m].m_hasTexture) {
      glBindTexture(GL_TEXTURE_2D, m_materials[m].m_tboHandle);
    } else {
      
    }
    
    glDrawArrays(GL_TRIANGLES, m_materials[m].m_offset,
                 m_materials[m].m_vertexCount);
  }
  glBindTexture(GL_TEXTURE_2D, 0);
  glBindVertexArray(0);

}

size_t Model::detachParent(Object * obj)
{   
   m_parents.remove(obj);
   return m_parents.size();
}

void Model::attachParent(Object * obj)
{
  m_parents.push_back(obj);
}